/**
 *@file main.cpp
 *@author lynsdk team
 *@version v1.0
 *@date 2022-09-23
 *@par Copyright:
 *© 2018 北京灵汐科技有限公司 版权所有。\n
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。\n
 *© 2018 Lynxi Technologies Co., Ltd. All rights reserved. \n
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 *This file can not be copied or distributed without the permission of Lynxi
 *Technologies Co., Ltd.
 *@brief This file describes the sample for using ipe inference on lynxi KA200
 *ipe
 */

#include <lyn_api.h>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include "BMPImage.h"
#include "logging.h"
#include "opencv2/opencv.hpp"
#include "util.hpp"
#include "CLI/CLI11.hpp" // 引入 CLI11 头文件

using namespace std;

size_t g_deviceID = 0;
string g_inputPath;
size_t g_isSaveBmp = 1;
string g_outputPath = "../data/";

bool ParseCmdArgs(int argc, char **argv)
{
  CLI::App app;
  // 添加选项
  app.add_option("-i", g_inputPath, "input file path")->required();
  app.add_option("-o", g_outputPath, "output file path")->default_val("../data/");
  app.add_option("-d", g_deviceID, "lynxi device id")->default_val(0);
  app.add_option("-s", g_isSaveBmp, "0 not save; 1 save.")->default_val(1);
  CLI11_PARSE(app, argc, argv);

  if (g_inputPath.empty())
  {
    cout << "error. input file path must set!" << endl;
    return false;
  }

  return true;
}

/**
 *@brief 同步callback函数，保存经过图像处理后的图像文件
 *param [in] result 输出图像描述
 */
int DumpResult(void *result)
{
  const string basename = "result";
  string suffix;
  string outputPath = g_outputPath + basename;

  char *buf;
  void *outData;
  size_t bufSize;
  int32_t width, height;
  lynPixelFormat_t format;

  CHECK_ERR(lynIpeGetPicDesc(result, &outData, &width, &height, &format));
  bufSize = width * height * 3;
  buf = new char[bufSize];

  CHECK_ERR(lynMemcpy(buf, outData, bufSize, ServerToClient));

  suffix = "_" + to_string(width) + "_" + to_string(height);
  outputPath += suffix + ".bgr";

  ofstream os(outputPath);
  if (!os)
  {
    cout << "open err " << outputPath << endl;
    return -1;
  }
  os.write(buf, bufSize);
  os.close();

  if (g_isSaveBmp)
  {
    CHECK_ERR(
        SaveBmp(g_outputPath + "result" + suffix + ".bmp", buf, width, height));
  }

  delete[] buf;
  return 0;
}

/**
 *@brief 读取输入图像文件并拷贝到server侧
 *param [in] data 输入文件地址
 *param [in] size 输入图像大小
 *param [in] path 输入图像地址
 *@return 成功返回0, 否则返回-1
 */
int ReadFile(void *data, size_t size, const string &path)
{
  unique_ptr<char[]> tmpData(new char[size]);

  ifstream is(path);

  if (!is)
  {
    cout << "open file err " << path << endl;
    return -1;
  }

  is.read(tmpData.get(), size);
  is.close();

  CHECK_ERR(lynMemcpy(data, tmpData.get(), size, ClientToServer));
  return 0;
}

int main(int argc, char *argv[])
{
  if (!ParseCmdArgs(argc, argv))
  {
    return -1;
  }

  logi("ipe sample begin");

  // 1 创建context
  lynContext_t context;
  CHECK_ERR(lynCreateContext(&context, g_deviceID));
  CHECK_ERR(lynRegisterErrorHandler(StreamErrorHandler, nullptr));

  // 2 创建stream
  lynStream_t stream;
  CHECK_ERR(lynCreateStream(&stream));

  lynIpePicDesc_t input, output;
  void *inData, *outData, *lastData;
  int32_t inWidth, inHeight, outWidth, outHeight;
  lynPixelFormat_t inFormat, outFormat;
  size_t inSize, outSize;
  lynIpeConfigDesc_t config;
  int32_t passageID = 0;
  int32_t lastWidth = 1280;
  int32_t lastHeigth = 1280;
  int lastsize = 1280 * 1280 * 3;
  CHECK_ERR(lynMalloc(&lastData, lastWidth * lastHeigth * 3));
  CHECK_ERR(lynMemset(lastData, 0, lastWidth * lastHeigth * 3));

  // 3 创建图像描述和配置描述
  CHECK_ERR(lynIpeCreatePicDesc(&input));
  CHECK_ERR(lynIpeCreatePicDesc(&output));
  CHECK_ERR(lynIpeCreateConfigDesc(&config));

  // 4 重置图像和配置描述
  CHECK_ERR(lynIpeResetPicDesc(input));
  CHECK_ERR(lynIpeResetPicDesc(output));
  CHECK_ERR(lynIpeResetConfigDesc(config));

  inWidth = 3840;
  inHeight = 2160;
  inFormat = LYN_PIX_FMT_NV12;

  inSize = inWidth * inHeight * 3 / 2;

  // 读取输入图像，并在拷贝至server侧
  CHECK_ERR(lynMalloc(&inData, inSize));
  CHECK_ERR(ReadFile(inData, inSize, g_inputPath));

  // 5 设置输入图像描述
  CHECK_ERR(lynIpeSetInputPicDesc(input, inData, inWidth, inHeight, inFormat));

  logi("set ipe parameter");

  // 6 配置相关图像处理模块的参数

  CHECK_ERR(lynIpeSetResizeConfig(config, 1280, 720)); // 缩小三倍
  CHECK_ERR(lynIpeSetC2CConfig(config, LYN_PIX_FMT_RGB24, 0));

  passageID = 0;

  // 1） 选择高速通路，即选定各模块的先后处理顺序
  CHECK_ERR(lynIpeCalOutputPicDesc(output, input, config, passageID));

  // 2） 获取处理后输出的宽高和图像格式 申请内存空间 设置输出图像地址
  CHECK_ERR(
      lynIpeGetPicDesc(output, nullptr, &outWidth, &outHeight, &outFormat));
  CHECK_ERR(lynIpeSetOutputPicData(
      output, reinterpret_cast<char *>(static_cast<unsigned char *>(lastData) +
                                       1280 * 560 * 3)));

  // 8 执行图像预处理
  CHECK_ERR(lynIpeProcessAsync(stream, input, output, config));

  // 插入同步回调函数，在回调函数中保存处理后的图像
  CHECK_ERR(lynStreamAddCallback(stream, DumpResult, output));

  // 9  等待处理后的图像保存完成
  CHECK_ERR(lynSynchronizeStream(stream));

#if 1
  char *buf = new char[lastsize];

  CHECK_ERR(lynMemcpy(buf, lastData, lastsize, ServerToClient));

  ofstream os("./last.rgb");
  if (!os)
  {
    cout << "open err " << endl;
    return -1;
  }
  os.write(buf, lastsize);
  os.close();
  delete[] buf;
#endif

  logi("ipe process and save output finish");

  // 10 销毁配置描述、图像描述、释放内存
  CHECK_ERR(lynIpeDestroyPicDesc(input));
  CHECK_ERR(lynIpeDestroyPicDesc(output));
  CHECK_ERR(lynIpeDestroyConfigDesc(config));
  CHECK_ERR(lynFree(inData));
  CHECK_ERR(lynFree(lastData));
  // 11 销毁stream
  CHECK_ERR(lynDestroyStream(stream));

  // 12 销毁context
  CHECK_ERR(lynDestroyContext(context));

  return 0;
}
